package com.demo.example.impl;

import cn.lingyangwl.framework.tool.core.CollectionUtils;
import cn.lingyangwl.framework.tool.core.ReflectUtils;
import com.demo.annotation.UpdatePlus;
import com.demo.annotation.UpdateReplace;
import com.demo.example.ElasticsearchRestTemplateExample;
import com.demo.model.entity.Book;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.IndexOperations;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.IndexQueryBuilder;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.*;

/**
 * @author shenguangyang
 */
@Service
public class ElasticsearchRestTemplateExampleImpl implements ElasticsearchRestTemplateExample<Book> {
    @Resource
    ElasticsearchRestTemplate elasticsearchRestTemplate;

    @Override
    public void createIndex(Class<Book> tClass) throws IOException {
        // 创建索引，会根据TestPojo的@Document注解信息来创建
        IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(tClass);
        // 创建索引，会根据TestPojo的@Document注解信息来创建,在程序启动时候会自动创建所有的
        // 标注 @Document 实体类索引到es中,因此当你程序运行时候删除索引后，如果在停止再启动后调用 indexOperations.create();
        // 会报错，提示索引已经存在
        System.out.println("exists: " + indexOperations.exists());
        if (indexOperations.exists()) {
            return;
        }
        boolean createIndex = indexOperations.create();
        System.out.println("createIndex: " + createIndex);

        // 配置映射, 会根据 TestPojo 类中的id、Field等字段来自动完成映射
        Document mapping = indexOperations.createMapping();
        System.out.println("mapping: " + mapping);
    }

    @Override
    public void createIndex(String indexName) throws IOException {
        // 创建索引，会根据TestPojo的@Document注解信息来创建
        IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(IndexCoordinates.of(indexName));
        System.out.println("exists: " + indexOperations.exists());
        if (indexOperations.exists()) {
            return;
        }
        boolean createIndex = indexOperations.create();
        System.out.println("createIndex: " + createIndex);

        // 配置映射，会根据 TestPojo 类中的id、Field等字段来自动完成映射
        // 因为 indexOps 传递的不是一个类所以不能创建mapping, 因为找不到具体的实体类
//        Document mapping = indexOperations.createMapping();
//        System.out.println("mapping: " + mapping);
    }

    @Override
    public void indexIsExists(Class<Book> tClass) throws IOException {
        IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(tClass);
        System.out.println("exists: " + indexOperations.exists());
    }

    @Override
    public void deleteIndex(Class<Book> tClass) throws IOException {
        IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(tClass);
        System.out.println("exists: " + indexOperations.exists());
        if (!indexOperations.exists()) {
            return;
        }
        boolean delete = indexOperations.delete();
        System.out.println("delete: " + delete);
    }

    @Override
    public void deleteIndex(String indexName) throws IOException {
        IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(IndexCoordinates.of(indexName));
        System.out.println("exists: " + indexOperations.exists());
        if (!indexOperations.exists()) {
            return;
        }
        boolean delete = indexOperations.delete();
        System.out.println("delete: " + delete);
    }

    @Override
    public void saveDocument(String indexName, Book book) throws IOException {
        /*
         * index用于新增或者修改整个文档
         * 文档不存在，执行新增操作
         * 文档存在，执行修改操作
         */
        String result = elasticsearchRestTemplate.index(new IndexQueryBuilder().withObject(book).build(),
                IndexCoordinates.of(indexName));
        System.out.println("result: " + result);
    }

    @Override
    public void isExistDocument(Class<Book> tClass, String id) throws IOException {
        Book book = elasticsearchRestTemplate.get(id, tClass);
        System.out.println(book != null);
    }

    @Override
    public void getDocumentById(Class<Book> tClass, String id) throws IOException {
        Book book = elasticsearchRestTemplate.get(id, tClass);
        System.out.println(book);
    }

    @Override
    public void paging(int pageNum, int pageSize, String index, Class<Book> bookClass) {
        int page = Math.max(pageNum, 0);

        // 2. 构建查询
        NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
                .withQuery(QueryBuilders.matchAllQuery())
                .withPageable(PageRequest.of(page, pageSize))
                .withSorts(new FieldSortBuilder("sales").order(SortOrder.DESC))
                .build();
        String dsl = Objects.requireNonNull(nativeSearchQuery.getQuery()).toString();
        System.out.println("dsl = " + dsl);

        /*
         * 3. 开始查询
         */
        SearchHits<Book> testBList = elasticsearchRestTemplate.search(nativeSearchQuery, bookClass, IndexCoordinates.of(index));
        long total = testBList.getTotalHits();

        List<SearchHit<Book>> searchHits = testBList.getSearchHits();
        for (SearchHit<Book> searchHit : searchHits) {
            Book book = searchHit.getContent();
            System.out.println("book = " + book);
        }

        System.out.println("total = " + total);
        System.out.println("pageNum = " + page);
        System.out.println("pageSize = " + pageSize);

    }

    @Override
    public void updateDocumentById(String indexName, Book book) throws IOException {
        String update = elasticsearchRestTemplate.index(new IndexQueryBuilder().withObject(book).build(),
                IndexCoordinates.of(indexName));
        System.out.println("update: " + update);
    }

    @Override
    public void updateDocumentBatch(List<Book> list, String indexName) throws NoSuchFieldException, IllegalAccessException {
        List<UpdateQuery> updateQueryList = new ArrayList<>();
        for (Book t : list) {
            Map<String,Object> scriptMap = getUpdateScript(t);
            // 构建需要更新的字段
            UpdateQuery updateQuery = UpdateQuery.builder(t.getId())
                    .withParams((Map<String, Object>) scriptMap.get("params"))
                    .withScript((String) scriptMap.get("script"))
                    .withLang("painless")  // 使用painless语言和提供的参数创建内联脚本
                    .build();
            updateQueryList.add(updateQuery);
        }
        elasticsearchRestTemplate.bulkUpdate(updateQueryList,IndexCoordinates.of(indexName));
        System.out.println("update success");
    }

    /**
     * 获取批量更新的脚本
     * @param t 实体类
     */
    public Map<String,Object> getUpdateScript(Book t) throws NoSuchFieldException, IllegalAccessException {
        StringBuilder script = new StringBuilder();
        // 通过反射实现
        // 更新文章内容
        Map<String, Object> map = new HashMap<>();
        Map<String, Object> params = new HashMap<>();

        Iterator<Map.Entry<Field, UpdatePlus>> fieldAnnotation = ReflectUtils.getFieldAnnotation(t, UpdatePlus.class);
        while (fieldAnnotation.hasNext()) {
            Map.Entry<Field, UpdatePlus> next = fieldAnnotation.next();
            Field field = next.getKey();
            String fieldName = field.getName();
            Object fieldValue = ReflectUtils.getFieldValue(t, fieldName);
            if (fieldValue != null)  {
                script.append(";").append("ctx._source.")
                        .append(fieldName).append(" += ").append("params.").append(fieldName);
                params.put(fieldName, fieldValue);
            }

        }

        Iterator<Map.Entry<Field, UpdateReplace>> fieldReplace = ReflectUtils.getFieldAnnotation(t, UpdateReplace.class);
        while (fieldReplace.hasNext()) {
            Map.Entry<Field, UpdateReplace> next = fieldReplace.next();
            Field field = next.getKey();
            String fieldName = field.getName();
            Object fieldValue = ReflectUtils.getFieldValue(t, fieldName);
            if (fieldValue != null) {
                script.append(";").append("ctx._source.")
                        .append(fieldName).append(" = ").append("params.").append(fieldName);
                params.put(fieldName, fieldValue);
            }

        }

        script.replace(0,1,"");
        map.put("params",params);
        map.put("script",script.toString());
        return map;
    }

    /**
     * 获取更新部分字段的脚本 (在原有的基础上进行相加)
     * @param params
     * @return
     */
    public String getPartialUpdateAddScript(Map<String, Object> params) {
        StringBuilder script = new StringBuilder();
        // 拼接脚本
        for (String key : params.keySet()) {
            script.append(";").append("ctx._source.").append(key).append(" += ").append("params.").append(key);
        }
        script.replace(0,1,"");
        return script.toString();
    }
    /**
     * 获取更新字段的脚本 (替换原有的字段)
     * @param params
     * @return
     */
    public String getPartialUpdateReplaceScript(Map<String, Object> params) {
        StringBuilder script = new StringBuilder();
        // 拼接脚本
        for (String key : params.keySet()) {
            script.append(";").append("ctx._source.").append(key).append(" = ").append("params.").append(key);
        }
        script.replace(0,1,"");
        return script.toString();
    }

    @Override
    public void deleteDocumentById(String indexName, String id) throws IOException {
        String deleteResult = elasticsearchRestTemplate.delete(id, IndexCoordinates.of(indexName));
        System.out.println("success: " + deleteResult);
    }

    @Override
    public void saveDocumentBatch(List<Book> books) throws IOException {
        Iterable<Book> save = elasticsearchRestTemplate.save(books);
        System.out.println("save success");
    }

    @Override
    public void searchDocument(String indexName, String keyword) throws IOException {
        int page = 0;
        int size = 1;
        // 1. 构建查询条件
        // 多字段匹配查询
        MatchQueryBuilder titleMatch = QueryBuilders.matchQuery("name", keyword).boost(1.0f);
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
        boolQueryBuilder.should(titleMatch);
        MatchQueryBuilder contentTextMatch = QueryBuilders.matchQuery("content", keyword).boost(1.0f);
        boolQueryBuilder.should(contentTextMatch);
        // 查询某个id的数据
        // boolQueryBuilder.must(QueryBuilders.termQuery("id", 1));

        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder()
                .withQuery(boolQueryBuilder)
                .withPageable(PageRequest.of(page, size))
                .withSorts(new FieldSortBuilder("sales").order(SortOrder.DESC));
        nativeSearchQueryBuilder.withHighlightFields(
                new HighlightBuilder.Field("name").preTags("<span style=\"color:red\">").postTags("</span>"),
                new HighlightBuilder.Field("content").preTags("<span style=\"color:red\">").postTags("</span>")
        );

        SearchHits<Book> searchHitList = elasticsearchRestTemplate.search(nativeSearchQueryBuilder.build(), Book.class,
                IndexCoordinates.of(indexName));

        long total = searchHitList.getTotalHits();
        List<SearchHit<Book>> searchHits = searchHitList.getSearchHits();

        for (SearchHit<Book> searchHit : searchHits) {
            Book book = searchHit.getContent();
            // 高亮字段
            Map<String, List<String>> highlightFields = searchHit.getHighlightFields();
//            for (Map.Entry<String, List<String>> entry : highlightFields.entrySet()) {
//                StringBuilder buffer = new StringBuilder();
//                for (String data : entry.getValue()) {
//                    buffer.append(data);
//                }
//                System.out.println("highlight: " + buffer);
//            }
//            System.out.println("hit: " + book);


            List<String> highlightNames = highlightFields.get("name");
            if (CollectionUtils.isNotEmpty(highlightNames)) {
                StringBuilder buffer = new StringBuilder();
                for (String highlightName : highlightNames) {
                    buffer.append(highlightName);
                }
                System.out.println("highlight: " + buffer);
                book.setName(buffer.toString());
            }

            List<String> highlightContents = highlightFields.get("content");
            if (CollectionUtils.isNotEmpty(highlightContents)) {
                StringBuilder buffer = new StringBuilder();
                for (String highlightContent : highlightContents) {
                    buffer.append(highlightContent);
                }
                System.out.println("highlight: " + buffer);
                book.setContent(buffer.toString());
            }
            System.out.println("hit: " + book);
        }
        System.out.println("total: " + total);
    }
}
